using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using WPCoco.Helpers;
using System.Diagnostics;

namespace WPCoco.Particles
{
    public partial class ParticleSystem
    {
        public ParticleSystem()
        {
            Active = true;
        }

        float elapsedTime;
        public float ElapsedTime
        {
            get { return elapsedTime; }
            set { elapsedTime = value; }
        }

        GravitySystemModeData gravitySystemData = new GravitySystemModeData();

        public GravitySystemModeData GravitySystemData
        {
            get { return gravitySystemData; }
            private set { gravitySystemData = value; }
        }

        private RadialSystemModeData radialSystemData = new RadialSystemModeData();

        public RadialSystemModeData RadialSystemData
        {
            get { return radialSystemData; }
            private set { radialSystemData = value; }
        }

        float emitCounter;
        public float EmitCounter
        {
            get { return emitCounter; }
            set { emitCounter = value; }
        }

        bool active;
        public bool Active
        {
            get { return active; }
            set { active = value; }
        }

        int particleCount = 0;
        public int ParticleCount
        {
            get { return particleCount; }
            set { particleCount = value; }
        }

        float duration = -1;
        public float Duration
        {
            get { return duration; }
            set { duration = value; }
        }

        Vector2 sourcePosition;
        public Vector2 SourcePosition
        {
            get { return sourcePosition; }
            set { sourcePosition = value; }
        }

        Vector2 positionVariance = Vector2.Zero;
        public Vector2 PositionVariance
        {
            get { return positionVariance; }
            set { positionVariance = value; }
        }

        float particleLife;
        public float ParticleLife
        {
            get { return particleLife; }
            set { particleLife = value; }
        }

        float particleLifeVariation;
        public float ParticleLifeVariation
        {
            get { return particleLifeVariation; }
            set { particleLifeVariation = value; }
        }

        float particleAngle;
        public float ParticleAngle
        {
            get { return particleAngle; }
            set { particleAngle = value; }
        }

        float particleAngleVariation;
        public float ParticleAngleVariation
        {
            get { return particleAngleVariation; }
            set { particleAngleVariation = value; }
        }

        float startSize;
        public float StartSize
        {
            get { return startSize; }
            set { startSize = value; }
        }

        float startSizeVariation;
        public float StartSizeVariation
        {
            get { return startSizeVariation; }
            set { startSizeVariation = value; }
        }

        float endSize;
        public float EndSize
        {
            get { return endSize; }
            set { endSize = value; }
        }

        float endSizeVariation;
        public float EndSizeVariation
        {
            get { return endSizeVariation; }
            set { endSizeVariation = value; }
        }

        Vector4 startColor;
        public Vector4 StartColor
        {
            get { return startColor; }
            set { startColor = value; }
        }

        Vector4 startColorVariation;
        public Vector4 StartColorVariation
        {
            get { return startColorVariation; }
            set { startColorVariation = value; }
        }

        Vector4 endColor;
        public Vector4 EndColor
        {
            get { return endColor; }
            set { endColor = value; }
        }

        Vector4 endColorVariation;
        public Vector4 EndColorVariation
        {
            get { return endColorVariation; }
            set { endColorVariation = value; }
        }

        float startSpin;
        public float StartSpin
        {
            get { return startSpin; }
            set { startSpin = value; }
        }

        float startSpinVariation;
        public float StartSpinVariation
        {
            get { return startSpinVariation; }
            set { startSpinVariation = value; }
        }

        float endSpin;
        public float EndSpin
        {
            get { return endSpin; }
            set { endSpin = value; }
        }

        float endSpinVariation;
        public float EndSpinVariation
        {
            get { return endSpinVariation; }
            set { endSpinVariation = value; }
        }

        float emissionRate;
        public float EmissionRate
        {
            get { return emissionRate; }
            set { emissionRate = value; }
        }

        int totalParticles;
        public int TotalParticles
        {
            get { return totalParticles; }
            set 
            { 
                totalParticles = value;
                EnsureCapasity();
            }
        }

        bool isAdditive = false;
        public bool IsAdditive
        {
            get { return isAdditive; }
            set { isAdditive = value; }
        }

        bool nonPremultiplied = false;
        public bool NonPremultiplied
        {
            get { return nonPremultiplied; }
            set { nonPremultiplied = value; }
        }

        bool autoRemoveOnFinish = true;
        public bool AutoRemoveOnFinish
        {
            get { return autoRemoveOnFinish; }
            set { autoRemoveOnFinish = value; }
        }

        ParticleMode mode = ParticleMode.Gravity;
        public ParticleMode Mode
        {
            get { return mode; }
            set { mode = value; }
        }

        string textureKey = null;
        public string TextureKey
        {
            get { return textureKey; }
            set 
            {
                if (textureKey == value) return;
                textureKey = value;
                hasTextureChange = true;
            }
        }

        Texture2D texture = null;
        bool hasTextureChange = true;

        public Texture2D GetTexture() 
        {
            if (textureKey == null) return null;
            if (hasTextureChange) 
            {
                texture = Coco.Load<Texture2D>(textureKey);
                hasTextureChange = false;
            }
            return texture;
        }

        LinkedList<Particle> particles = new LinkedList<Particle>();
        public LinkedList<Particle> Particles
        {
            get { return particles; }
        }

        public void AdvanceInTime(float deltaTime) 
        {
            float slice = 0.1f;
            int count = (int)(1 + deltaTime / slice);
            for (int i = 0; i < count; i++)
                Update(slice);
        }

        void EnsureCapasity()
        {
            ParticleStore.EnsureCapacity(totalParticles);
        }

        LinkedListNode<Particle> ReuseOneNode() 
        {
            return ParticleStore.ReuseOneNode();
        }

        void InitializeParticle(Particle particle) 
        {
            // timeToLive
            // no negative life. prevent division by 0
            particle.TimeToLive = Math.Max(particleLife + particleLifeVariation * MiscHelper.GetRandomMinusOneToOne(), 0);

            // Position
            particle.Position = new Vector2(sourcePosition.X + positionVariance.X * MiscHelper.GetRandomMinusOneToOne(),
                                            sourcePosition.Y + positionVariance.Y * MiscHelper.GetRandomMinusOneToOne());

            // Color
            particle.Color = new Vector4(
                MathHelper.Clamp(startColor.X + startColorVariation.X * MiscHelper.GetRandomMinusOneToOne(), 0, 1),
                MathHelper.Clamp(startColor.Y + startColorVariation.Y * MiscHelper.GetRandomMinusOneToOne(), 0, 1),
                MathHelper.Clamp(startColor.Z + startColorVariation.Z * MiscHelper.GetRandomMinusOneToOne(), 0, 1),
                MathHelper.Clamp(startColor.W + startColorVariation.W * MiscHelper.GetRandomMinusOneToOne(), 0, 1));

            Vector4 endc = new Vector4(
                MathHelper.Clamp(endColor.X + endColorVariation.X * MiscHelper.GetRandomMinusOneToOne(), 0, 1),
                MathHelper.Clamp(endColor.Y + endColorVariation.Y * MiscHelper.GetRandomMinusOneToOne(), 0, 1),
                MathHelper.Clamp(endColor.Z + endColorVariation.Z * MiscHelper.GetRandomMinusOneToOne(), 0, 1),
                MathHelper.Clamp(endColor.W + endColorVariation.W * MiscHelper.GetRandomMinusOneToOne(), 0, 1));

            particle.DeltaColor = (endc - particle.Color) / particle.TimeToLive;

            // size
            particle.Size = Math.Max(startSize + startSizeVariation * MiscHelper.GetRandomMinusOneToOne(), 0);

            if (endSize == -1)
            {
                particle.DeltaSize = 0;
            }
            else
            {
                float ends = Math.Max(endSize + endSizeVariation * MiscHelper.GetRandomMinusOneToOne(), 0);
                particle.DeltaSize = (ends - particle.Size) / particle.TimeToLive;
            }

            // rotation
            float starta = MathHelper.ToRadians(startSpin + startSpinVariation * MiscHelper.GetRandomMinusOneToOne());
            float enda = MathHelper.ToRadians(endSpin + endSpinVariation * MiscHelper.GetRandomMinusOneToOne());
            particle.Rotation = starta;
            particle.DeltaRotation = (enda - starta) / particle.TimeToLive;

            particle.StartPosition = sourcePosition;

            // direction
            float a = MathHelper.ToRadians(particleAngle + particleAngleVariation * MiscHelper.GetRandomMinusOneToOne());

            // Mode Gravity: A
            if (mode == ParticleMode.Gravity)
            {
                if (particle.GravityData == null)
                    particle.GravityData = new GravityModeData();

                Vector2 v = new Vector2((float)Math.Cos(a), (float)Math.Sin(a));
                float s = gravitySystemData.Speed + gravitySystemData.SpeedVariation * MiscHelper.GetRandomMinusOneToOne();

                // direction
                particle.GravityData.Direction = v * s;

                // radial accel
                particle.GravityData.RadialAcceleration = gravitySystemData.RadialAcceleration +
                                                          gravitySystemData.RadialAccelerationVariation * MiscHelper.GetRandomMinusOneToOne();

                // tangential accel
                particle.GravityData.TangencialAcceleration = gravitySystemData.TangencialAcceleration +
                                                              gravitySystemData.TangencialAccelerationVariation * MiscHelper.GetRandomMinusOneToOne();
            }
            // Mode Radius: B
            else
            {
                if (particle.RadialData == null)
                    particle.RadialData = new RadialModeData();
                // Set the default diameter of the particle from the source position
                float startRadius = radialSystemData.StartRadius + radialSystemData.StartRadiusVariation * MiscHelper.GetRandomMinusOneToOne();
                float endRadius = radialSystemData.EndRadius + radialSystemData.EndRadiusVariation * MiscHelper.GetRandomMinusOneToOne();

                particle.RadialData.Radius = startRadius;

                if (radialSystemData.EndRadius == -1)
                    particle.RadialData.DeltaRadius = 0;
                else
                    particle.RadialData.DeltaRadius = (endRadius - startRadius) / particle.TimeToLive;

                particle.RadialData.Angle = MathHelper.ToDegrees(a);
                particle.RadialData.DegreesPerSecond = radialSystemData.RotationPerSecond + radialSystemData.RotationPerSecondVariation * MiscHelper.GetRandomMinusOneToOne();
            }	
        }

        void AddParticle() 
        {
            LinkedListNode<Particle> node = ParticleStore.ReuseOneNode();
            if (node == null) 
                node = new LinkedListNode<Particle>(new Particle());
            InitializeParticle(node.Value);
            particles.AddLast(node);
            particleCount++;
        }

        void RemoveParticle(LinkedListNode<Particle> node) 
        {
            particles.Remove(node);
            ParticleStore.RestoreNode(node);
            particleCount--;
        }

        void StopSystem()
        {
            active = false;
            elapsedTime = duration;
            emitCounter = 0;
        }

        bool isFinish = false;

        public bool IsFinish
        {
            get { return isFinish; }
            set { isFinish = value; }
        }

        public void Update(float elapsedTime)
        {
            if (isFinish) return;

            if (active && emissionRate != 0)
            {
                float rate = 1.0f / emissionRate;
                emitCounter += elapsedTime;
                while (particleCount < totalParticles && emitCounter > rate)
                {
                    AddParticle();
                    emitCounter -= rate;
                }

                this.elapsedTime += elapsedTime;
                if (duration != -1 && duration < this.elapsedTime)
                {
                    StopSystem();
                    return;
                }
            }

            Vector2 currentPosition = Vector2.Zero;
            //currentPosition = sourcePosition;

            LinkedListNode<Particle> piv, next;

            piv = particles.First;

            while (piv != null)
            {
                next = piv.Next;
                Particle p = piv.Value;

                // life
                p.TimeToLive -= elapsedTime;

                if (p.TimeToLive > 0)
                {
                    // Mode A: gravity, direction, tangential accel & radial accel
                    if (mode == ParticleMode.Gravity)
                    {
                        Vector2 tmp, radial, tangential;
                        radial = Vector2.Zero;
                        // radial acceleration
                        Vector2 pos = p.Position - p.StartPosition;
                        if (pos.X != 0 || pos.Y != 0)
                            radial = Vector2.Normalize(pos);
                        tangential = radial;
                        radial = radial * p.GravityData.RadialAcceleration;

                        // tangential acceleration
                        float newy = tangential.X;
                        tangential.X = -tangential.Y;
                        tangential.Y = newy;
                        tangential = tangential * p.GravityData.TangencialAcceleration;

                        // (gravity + radial + tangential) * dt
                        tmp = (radial + tangential + gravitySystemData.Gravity) * elapsedTime;
                        p.GravityData.Direction += tmp;
                        tmp = p.GravityData.Direction * elapsedTime;
                        p.Position += tmp;
                    }
                    else
                    {
                        // Update the angle and radius of the particle.
                        p.RadialData.Angle += p.RadialData.DegreesPerSecond * elapsedTime;
                        p.RadialData.Radius += p.RadialData.DeltaRadius * elapsedTime;
                        p.Position.X = -(float)Math.Cos(MathHelper.ToRadians(p.RadialData.Angle)) * p.RadialData.Radius;
                        p.Position.Y = -(float)Math.Sin(MathHelper.ToRadians(p.RadialData.Angle)) * p.RadialData.Radius;
                        p.Position += p.StartPosition;
                    }

                    // color
                    p.Color += p.DeltaColor * elapsedTime;

                    // size
                    p.Size += p.DeltaSize * elapsedTime;
                    if (p.Size < 0) p.Size = 0;

                    // angle
                    p.Rotation += p.DeltaRotation * elapsedTime;
                }
                else
                {
                    RemoveParticle(piv);
                    if (particleCount == 0 && autoRemoveOnFinish)
                    {
                        StopSystem();
                        IsFinish = true;
                        break;
                    }
                }

                piv = next;
            }
        }
    }
}
